﻿namespace UDKDevKitVSX
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Security.Permissions;
    using EnvDTE80;
    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio.TextManager.Interop;
    using UDKDevKitVSX.Classes;
    using UDKDevKitVSX.Interfaces;
    using UDKDevKitVSX.Utils;
    using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
    using IServiceProvider = System.IServiceProvider;
    using System.Collections.Generic;
    using Microsoft.Win32;
    using System.Runtime.InteropServices;
    using System.ComponentModel.Design;
    using System.Windows.Forms;
    using Microsoft.VisualStudio.OLE.Interop;

    [PermissionSetAttribute(SecurityAction.InheritanceDemand, Name = "FullTrust")]
    [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
    internal class VSXShellController : IUdkConduit, IDisposable
    {
        private DTE2 dte8Obj;
        private IVsOutputWindowPane logOutputWindowPane;
        private bool debugging;
        private IUdkDebugCommandsProcessorEx udkDebugCommandsProcessor;
        private VSCommandListener vsCommandsListener;
        private bool attachedToActiveObject;

        public VSXShellController(IUdkCommandInterface udkCommandInterface)
        {
            if (udkCommandInterface == null)
            {
                Debug.Fail("ArgumentNullException - udkCommandInterface");
                throw new ArgumentNullException("udkCommandInterface");
            }

            this.UDKCommandInterface = udkCommandInterface;
            this.vsCommandsListener = new VSCommandListener(udkCommandInterface);
        }

        internal IUdkCommandInterface UDKCommandInterface { get; private set; }

        protected DTE2 DTE
        {
            get
            {
                if (this.dte8Obj == null)
                {
                    Debug.WriteLine("Creating DTE instance for " + this.ProgID);

                    if (this.IsIsolatedShell)
                    {
                        Debug.WriteLine("Isolation Mode - finding active instance for " + this.ProgID);

                        this.dte8Obj = (DTE2)Marshal.GetActiveObject(this.ProgID);
                        this.attachedToActiveObject = true;
                    }

                    if (this.dte8Obj == null)
                    {
                        this.dte8Obj =
                            (DTE2)System.Activator.CreateInstance(
                                System.Type.GetTypeFromProgID(this.ProgID),
                                true);
                    }
                }

                return this.dte8Obj;
            }
        }

        protected IServiceProvider ServiceProvider
        {
            get
            {
                return new ServiceProvider((IOleServiceProvider)this.DTE);
            }
        }

        protected IVsOutputWindowPane LogOutputWindowPane
        {
            get
            {
                if (this.logOutputWindowPane == null)
                {
                    this.logOutputWindowPane = this.CreatePane(Guid.NewGuid(), "UDK Log", true, true);
                }

                return this.logOutputWindowPane;
            }
        }

        protected IUdkDebugCommandsProcessorEx RemoteExecutionController
        {
            get
            {
                if (this.udkDebugCommandsProcessor == null)
                {
                    this.udkDebugCommandsProcessor = new UDKConduitCommandSender();
                }

                return this.udkDebugCommandsProcessor;
            }
        }

        private bool IsIsolatedShell
        {
            get
            {
                var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Mr O. Duzhar");
                if (key != null)
                {
                    var progID = key.GetValue("ShellProgID");
                    if (progID != null)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        private string ProgID
        {
            get
            {
                var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Mr O. Duzhar");
                if (key != null)
                {
                    var progID = key.GetValue("ShellProgID");
                    if (progID != null)
                    {
                        return progID.ToString();
                    }
                }

                return "VisualStudio.DTE.10.0";
            }
        }

        public void ShowDllForm()
        {
            Debug.WriteLine("Show DLL Form. Activating window");

            this.DTE.ActiveWindow.Activate();

            if (!this.debugging)
            {
                Debug.WriteLine("Start Debugger...");

#if DEBUG
                Debug.WriteLine("DEBUG MODE.");
#else
                Debug.WriteLine("RELEASE MODE.");
#endif

                this.StartDebugger();
                this.debugging = true;
            }
            else
            {
                Debug.WriteLine("RemoteExecutionController... stopping...");

                this.RemoteExecutionController.Stop();
            }
        }

        public void BuildHierarchy()
        {
            throw new NotImplementedException();
        }

        public void ClearHierarchy()
        {
            throw new NotImplementedException();
        }

        public void AddClassToHierarchy(string className)
        {
            throw new NotImplementedException();
        }

        public void ClearWatch(int watchType)
        {
            throw new NotImplementedException();
        }

        public void ClearAWatch(int watchType)
        {
            throw new NotImplementedException();
        }

        public int AddAWatch(int watchType, int parentIndex, string varName, string varValue)
        {
            throw new NotImplementedException();
        }

        public void LockList(int watchList)
        {
            throw new NotImplementedException();
        }

        public void UnlockList(int watchList)
        {
            throw new NotImplementedException();
        }

        public void AddBreakpoint(string className, int lineNo)
        {
            throw new NotImplementedException();
        }

        public void RemoveBreakpoint(string className, int lineNo)
        {
            throw new NotImplementedException();
        }

        public void EditorLoadClass(string className)
        {
            this.RemoteExecutionController.EditorLoadClass(className);
        }

        private static void RegisterCustomDebuggerClass(string classId)
        {
            Debug.WriteLine("Copying CLSID entries");

            const string classPath = @"Software\Microsoft\UDKDevKitVSXShell\1.0{1}\CLSID\{0}";

            var sourceKey = Registry.CurrentUser.OpenSubKey(String.Format(classPath, classId, String.Empty));

            var key = Registry.CurrentUser.CreateSubKey(String.Format(classPath, classId, "_Config"));
            key.SetValue("Assembly", sourceKey.GetValue("Assembly"));
            key.SetValue("Class", sourceKey.GetValue("Class"));

#if LOCAL_PATH_DLL_DEBUG && DEBUG
            Debug.WriteLine("LOCAL DEBUG PATH.");

            var fileName = Path.GetFileName(sourceKey.GetValue("CodeBase").ToString());
            key.SetValue("CodeBase", Path.Combine(@"C:\Users\Alexander\Documents\Visual Studio 2010\Projects\UDKDevKitVSX\UDKDevKitVSXDebugEngine\bin\Debug", fileName));

#else
            key.SetValue("CodeBase", sourceKey.GetValue("CodeBase"));
#endif

            key.SetValue("InprocServer32", sourceKey.GetValue("InprocServer32"));
            key.Close();

            sourceKey.Close();

            Debug.WriteLine("Done.");
        }

        private void StartDebugger()
        {
            Debug.WriteLine(String.Concat("info::CurrentDirectory: ", Environment.CurrentDirectory));

            string sourceDirectorySubPath = String.Format("\\Development\\Src");

            string sourceDirectoryPath = Path.Combine(Environment.CurrentDirectory, String.Concat("..", sourceDirectorySubPath));

            Debug.WriteLine(String.Concat("info::CheckFilePath: ", sourceDirectoryPath));

            if (!Directory.Exists(sourceDirectoryPath))
            {
                sourceDirectoryPath = Path.Combine(Environment.CurrentDirectory, String.Concat("..\\..", sourceDirectorySubPath));

                Debug.WriteLine(String.Concat("info::CheckFilePath: ", sourceDirectoryPath));

                if (!Directory.Exists(sourceDirectoryPath))
                {
                    return;
                }
            }

            // Add setting to config file
            if (this.IsIsolatedShell)
            {
                RegisterCustomDebuggerClass("{C480698C-792C-4619-8640-FA93F291BF01}");
                RegisterCustomDebuggerClass("{CCD13A92-1C7F-418E-8951-F419A81DECBA}");
            }

            this.AttachToMenuItems();

            VsDebugTargetInfo2 info = new VsDebugTargetInfo2();
            info.cbSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(info);
            info.dlo = (int)Microsoft.VisualStudio.Shell.Interop.DEBUG_LAUNCH_OPERATION.DLO_CreateProcess;

            info.bstrExe = "dummy.exe";
            info.bstrCurDir = sourceDirectoryPath;
            info.bstrArg = null; // no command line parameters
            info.bstrRemoteMachine = null; // debug locally
            info.hStdOutput = 0; // Let stdout stay with the application.
            info.guidLaunchDebugEngine = new Guid("{179E815F-3C5E-4650-8E28-FC3C4EF6F92C}"); // Set the launching engine UDK Script Debug Engine guid
            info.LaunchFlags = 0;
            info.pUnknown = null;

            IntPtr pInfo = System.Runtime.InteropServices.Marshal.AllocCoTaskMem((int)info.cbSize);
            System.Runtime.InteropServices.Marshal.StructureToPtr(info, pInfo, false);

            var debug = this.Service<IVsDebugger2, SVsShellDebugger>();
            if (debug == null)
            {
                Debug.Fail("IVsDebugger2 is null.");
                return;
            }

            try
            {
                Debug.WriteLine("Launch Debug Targets...");

                Core.CheckOk(debug.LaunchDebugTargets2(1, pInfo));
            }
            finally
            {
                Debug.WriteLine("Launch finalling...");

                if (pInfo != IntPtr.Zero)
                {
                    System.Runtime.InteropServices.Marshal.FreeCoTaskMem(pInfo);
                }
            }
        }

        private void AttachToMenuItems()
        {
            Debug.WriteLine(">>>> SUIHostCommandDispatcher - IOleCommandTarget");

            var mcs = this.Service<IOleCommandTarget, SUIHostCommandDispatcher>();
            if (null != mcs)
            {
            }
            else
            {
                Debug.WriteLine("!!!! IOleCommandTarget is null.");
                Debug.Fail("IOleCommandTarget is null.");
            }

            Debug.WriteLine("<<<< SUIHostCommandDispatcher - IOleCommandTarget");
        }

        public void EditorGotoLine(int lineNo, int highlight)
        {
            this.RemoteExecutionController.EditorGotoLine(lineNo);
        }

        public void AddLineToLog(string text)
        {
            var logPane = this.LogOutputWindowPane;
            if (logPane != null)
            {
                logPane.OutputString(String.Concat(text, Environment.NewLine));
            }
        }

        public void CallStackClear()
        {
            this.RemoteExecutionController.CallStackClear();
        }

        public void CallStackAdd(string callStackEntry)
        {
            this.RemoteExecutionController.CallStackAdd(callStackEntry);
        }

        public void SetCurrentObjectName(string objectName)
        {
            throw new NotImplementedException();
        }

        public void DebugWindowState(int stateCode)
        {
            throw new NotImplementedException();
        }

        public void AddWatches(int watchList, IList<AddWatchRecord> properties)
        {
            this.RemoteExecutionController.AddWatches(watchList, properties);
        }

        public void ShutDown()
        {
            this.Dispose();
        }

        protected IVsOutputWindowPane CreatePane(Guid paneGuid, string title, bool visible, bool clearWithSolution)
        {
            try
            {
                var output = this.Service<IVsOutputWindow, SVsOutputWindow>();
                IVsOutputWindowPane pane;

                // Create a new pane.
                output.CreatePane(
                    ref paneGuid,
                    title,
                    Convert.ToInt32(visible),
                    Convert.ToInt32(clearWithSolution));

                // Retrieve the new pane.
                output.GetPane(ref paneGuid, out pane);

                return pane;
            }
            catch (Exception)
            {
            }

            return null;
        }

        protected I Service<I, S>()
        {
            return (I)this.ServiceProvider.GetService(typeof(S));
        }

        public void Dispose()
        {
            Debug.WriteLine("Dispose.");

            if (this.udkDebugCommandsProcessor != null)
            {
                Debug.WriteLine("Terminating Debugging Process.");

                this.udkDebugCommandsProcessor.TerminateDebuggingProcess();
            }

            if (this.dte8Obj != null)
            {
                if (!this.attachedToActiveObject)
                {
                    Debug.WriteLine("DTE Application Quit.");

                    this.DTE.Application.Quit();
                }
            }

            this.logOutputWindowPane = null;
            this.udkDebugCommandsProcessor = null;
            this.vsCommandsListener = null;
            this.dte8Obj = null;
        }
    }
}
